iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

昨天的文章我們增加 MongoDB 的支持,也得到了 local MongoDB,可以來寫 CRUD 的操作。 Panache Mongodb 提供了 Repository 風格的資料庫存取方式而不需操作 MongoDB 的 Document. 不過這裡的 Entity 比較需要是 var 的 變數 , 所以還是跟本來的 model 有所區別,但我們可以利用 Kotlin 的 map, let 輕鬆的轉換。

以下會說明 Resource -> Service -> MongoRepository 的操作

程式碼已放置於 Github : https://github.com/hmchangm/getting-start-QK/tree/ironman2022-d8

FilmEntity

建立 FilmEntity , 而且要用 MongoEntity 宣告 db 的 collection

//FilmEntity
@MongoEntity(collection = "film")
data class FilmEntity(
    var id: ObjectId? = null,
    var title: String,
    var episodeId: Int,
    var director: String,
    var releaseDate: LocalDate
)

Film Repository

Panache 提供了 Reactive 的實現,所以我們會 extends ReactivePanacheMongoRepository, 基本的 persist, update, delete, findAll 會提供,也很容易擴展。

@ApplicationScoped
class FilmRepository : ReactivePanacheMongoRepository<FilmEntity> {
    suspend fun findByEpisodeId(id: Int) = find("episodeId", id).firstResult().awaitSuspending()
    suspend fun findAllAsync() = findAll().list().awaitSuspending()
}

awaitSuspending 用 kotlin 簡化你的 reactive

ReactivePanacheMongoRepository 的方法回的都是一個 Uni 的封裝。但我們可利用 awaitSuspending 方法解成 suspending function,這就是 Kotlin 能夠簡化 async 操作。 這樣就可以跟 API suspend function 一路串下來。因為 suspend function 一定要call suspend function。

FilmService

所以可以看到我們的 CRUD 的操作就都會穿插 awaitSuspending()

@ApplicationScoped
class FilmService(val filmRepository: FilmRepository) {
    suspend fun getAllFilms() = filmRepository.findAllAsync().map(entityToModel)
    suspend fun getFilmCount() = filmRepository.count().awaitSuspending()
    suspend fun getFilm(id: Int) = filmRepository.findByEpisodeId(id)?.let(entityToModel)
    suspend fun save(film: Film) = film.let(modelToEntity)
        .let(filmRepository::persist)
        .awaitSuspending().let(entityToModel)
    ....
}

Entity to Model 換來換去, 兼談 function type

因為對 Mongodb 操作我們等於是有一個 DTO 的操作。所以會有 Entity -> Model , Model -> Entity 的function 需要。Kotlin 可以直接用變數 function type 來達成。對我們的語意表達也比較容易。

    private val modelToEntity: (Film) -> (FilmEntity) = { film ->
        FilmEntity(
            title = film.title,
            episodeId = film.episodeID,
            director = film.director,
            releaseDate = film.releaseDate.toJavaLocalDate()
        )
    }

    private val entityToModel: (FilmEntity) -> (Film) = {
        Film(it.title, it.episodeId, it.director, it.releaseDate.toKotlinLocalDate())
    }

這些 funciton type 就可以很容易的被 map, let 所利用。例如把從 mongodb 來的所有 entity 都轉成 model,就可以直接 apply function type。所謂的 High Order Function 操作

    suspend fun getAllFilms() = filmRepository.findAllAsync().map(entityToModel)
    suspend fun getFilm(id: Int) = filmRepository.findByEpisodeId(id)?.let(entityToModel)

Resource Layer

那 API Layer 就會變的很簡單,主要是 call service layer 操作


@Path("/films")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class FilmResource(val filmService: FilmService) {

    @GET
    suspend fun list() = filmService.getAllFilms()

    @GET
    @Path("/{id}")
    suspend fun getById(id: Int) = filmService.getFilm(id)

    @POST
    suspend fun add(film: Film) = filmService.save(film)

    @PUT
    @Path("/{id}")
    suspend fun update(film: Film) = filmService.update(film)

    @DELETE
    @Path("/{id}")
    suspend fun delete(id: Int) = filmService.delete(id)

    @GET
    @Path("/count")
    suspend fun count(): Long = filmService.getFilmCount()
}


最後我們還需要在 application.properties 加上 quarkus.mongodb.database=ironman 指定 database

這樣我們就完成了基本的 CRUD,而且前後都是用 kotlin suspend function 連起來,保證了 reactive.


上一篇
增加 Panache Kotlin, Quarkus 幫你起 MongoDB - Day7
下一篇
1) 修正 Data Class 轉換問題 2) 談談 Kotlin function 串串法 - Day9
系列文
Quarkus, Kotlin, Reactive 雲原生服務開發32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言